package com.iob.coin.blImpl.kgqa;

import com.iob.coin.bl.kgqa.KgQAService;
import com.iob.coin.covid19kg.dataset.DatasetManager;
import com.iob.coin.covid19kg.dataset.DatasetManagerLocalImpl;
import com.iob.coin.covid19kg.dataset.DefaultURI;
import com.iob.coin.covid19kg.exceptions.AnswerException;
import com.iob.coin.covid19kg.exceptions.QAServerException;
import com.iob.coin.covid19kg.exceptions.QuestionTreeException;
import com.iob.coin.covid19kg.full;
import com.iob.coin.covid19kg.kg.KGBuilder;
import com.iob.coin.covid19kg.naturallanguage.NaturalLanguageResolver;
import com.iob.coin.entity.*;
import com.iob.coin.entity.questionTree.*;
import com.iob.coin.entity.result.RDFNodeResult;
import com.iob.coin.entity.result.Result;
import com.iob.coin.entity.result.TargetResult;
import com.iob.coin.util.logutil.Logger;
import com.iob.coin.vo.AnswerVO;
import com.iob.coin.vo.ResponseVO;
import org.apache.jena.rdf.model.*;
import org.apache.jena.vocabulary.OWL;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.RDFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;

/**
 * @Author: GuoXinyuan
 * @Date: 2021/5/29
 */

@Service
public class KgQAServiceImpl implements KgQAService {

    @Autowired
    NaturalLanguageResolver languageResolver;
//            = new NaturalLanguageResolver();

    @Autowired
    DatasetManager datasetManager;
//            = new DatasetManagerLocalImpl();

    @Autowired
    KGBuilder kgBuilder;
//            = new KGBuilder();

    Logger logger = new Logger(this.getClass().getSimpleName(),System.out);

    Map<String, Object> qaList = new HashMap<>();

    /**
     * 问答入口
     * @param question 问题
     * @param step 每次返回答案步长 -1
     * @param cur 当前开始位置
     * @return ResponseVO
     */
    @Override
    public ResponseVO askInStep(String question, int step, int cur) {

        logger.log("ask","提出问题",new HashMap<String,String>(){{
            put("question",question);
            put("from",String.valueOf(cur));
            put("step",String.valueOf(step));
        }});

        //解析问题树
        try{
            //解析三元组
            QuestionTree questionTree = languageResolver.resolve(question);
            if(questionTree==null) return ResponseVO.buildSuccess(new AnswerVO());
            int level = questionTree.level();

            logger.log("ask","解析问题树成功",new HashMap<String,String>(){{
                put("level",String.valueOf(level));
            }});

            if(level == 0){ //三null
                return ResponseVO.buildFailure("无法解析问题");
            }else { //三元组问答
                questionTree = completeTree(questionTree);

                logger.log("ask","问题树补完成功",null);

                AnswerVO answerVO = doAsk(questionTree,step,cur);

                logger.log("ask","产生答案",null);

                return ResponseVO.buildSuccess(answerVO);
            }
        } catch (Exception e){
            e.printStackTrace();
            return ResponseVO.buildFailure("服务器异常");
        }
    }

    /**
     * 问答入口
     * @param question
     * @return
     */
    @Override
    public ResponseVO ask(String question){

        if(question.equals("*")) {
            return ResponseVO.buildSuccess(full.paintFullKG(datasetManager,kgBuilder));
        }

        logger.log("ask","提出问题",new HashMap<String,String>(){{
            put("question",question);
        }});

        //解析问题树
        try{
            //解析三元组
            QuestionTree questionTree = languageResolver.resolve(question);
            if(questionTree==null) return ResponseVO.buildSuccess(new AnswerVO());
            int level = questionTree.level();

            logger.log("ask","解析问题树成功",new HashMap<String,String>(){{
                put("level",String.valueOf(level));
            }});

            if(level == 0){ //三null
                return ResponseVO.buildFailure("无法解析问题");
            }else { //三元组问答
                questionTree = completeTree(questionTree);

                logger.log("ask","问题树补完成功",null);

                AnswerVO answerVO = doAsk(questionTree);

                logger.log("ask","产生答案",null);

                return ResponseVO.buildSuccess(answerVO);
            }
        } catch (Exception e){
            e.printStackTrace();
            return ResponseVO.buildFailure("服务器异常");
        }

    }

    /**
     * 获取问题的相关问题
     * @param uri
     * @return
     */
    @Override
    public ResponseVO askForRecommend(String uri) {
        Resource resource = datasetManager.getResource(uri);
        return ResponseVO.buildSuccess(getRecommends(resource));
    }

    /**
     * 将问题树为null的部分都填充为TargetTree
     * @param root
     */
    private QuestionTree completeTree(QuestionTree root) throws QuestionTreeException {
        //遍历节点栈
        Stack<QuestionTree> treeStack = new Stack<>();
        //遍历标记栈
        Stack<Integer> flagStack = new Stack<>();

        treeStack.push(root);
        flagStack.push(0);
        while(!treeStack.isEmpty()){

            QuestionTree curNode = treeStack.pop();
            int flag = flagStack.pop();

            if(curNode!=null) { //当前节点为空时不做处理
                //--- 三元组节点 继续遍历 ---
                if(curNode instanceof TripleTree){
                    TripleTree tripleTree = (TripleTree) curNode;
                    if(flag<=2){ //尚未遍历完成
                        flagStack.push(flag+1);
                        treeStack.push(tripleTree);
                        flagStack.push(0);
                        treeStack.push(tripleTree.getSPOArr()[flag]);
                    }else{ //遍历结束 进行归并
                        if(!tripleTree.isAskForResource()){ //只归并含有一个?的节点
                            completeTripleTree(tripleTree);
                        }
                    }
                }
                //--- 标签节点LabelTree 替换为MatchTree ---
                else if(curNode instanceof LabelTree){
                    LabelTree labelTree = (LabelTree) curNode;
                    List<RDFNode> rdfNodeList = datasetManager.getResourceByLabel(labelTree.getContent());
                    ResultTree resultTree = new ResultTree(new ArrayList<>());
                    //将匹配结果包装成RDFNodeResult
                    rdfNodeList.forEach((rdfNode)->{
                        RDFNodeResult rdfNodeResult = new RDFNodeResult(rdfNode);
                        resultTree.getResultList().add(rdfNodeResult);
                    });
                    //替换当前节点
                    if(treeStack.isEmpty()){
                        return resultTree;
                    }
                    TripleTree parent = (TripleTree) treeStack.peek();
                    parent.replaceLabelTree(resultTree,flagStack.peek()-1);
                }
                //其他类型节点不做处理
            }

        }

        return root;
    }

    /**
     * 已经能够进行归并的三元组 对 null位置进行填充
     * 只填充含有一个null形式的节点
     * 含有两个null的三元组只能位于根节点 因而不做填充 留给后续做处理
     * 含有三个null的三元组视为错误
     * @param tripleTree 需要归并的TripleTree节点 ！！！要求只含有一个?
     */
    private void completeTripleTree(TripleTree tripleTree) throws QuestionTreeException {
        ResultTree resultTree = new ResultTree(new ArrayList<>());

        //获取属性结果集
        ResultTree propertyTree = (ResultTree) tripleTree.getProperty();
        List<Result> propertyList = propertyTree.getResultList();

        //获取资源结果集
        ResultTree argumentTree = (ResultTree) tripleTree.getArgumentSubTree();
        List<Result> resourceList = argumentTree.getResultList();

        //进行搜索
        for (Result pr:propertyList){
            RDFNode property = pr.getRdfNode();
            //必须可以强转为Property
            if(!property.canAs(Property.class)) continue;
            Property p = property.as(Property.class);

            for(Result rr:resourceList){
                RDFNode rdfNode = rr.getRdfNode();
                //(s,p,?)形式
                if(!tripleTree.isAskForSubject()){
                    if(rdfNode.isResource()){
                        Resource r = rdfNode.asResource();
                        //进行查询
                        List<Statement> statements = datasetManager.listStatements(r,p,null);
                        for(Statement statement:statements){
                            TargetResult targetResult = new TargetResult(statement.getObject());
                            targetResult.setSourceSubject(rr);
                            targetResult.setSourceProperty(pr);
                            targetResult.setSourceObject(null);
                            resultTree.getResultList().add(targetResult);
                        }
                    }
                }
                //(?,p,o)形式
                else{
                    List<Statement> statements = datasetManager.listStatements(null,p,rdfNode);
                    for(Statement statement:statements){
                        TargetResult targetResult = new TargetResult(statement.getSubject());
                        targetResult.setSourceSubject(null);
                        targetResult.setSourceProperty(pr);
                        targetResult.setSourceObject(rr);
                        resultTree.getResultList().add(targetResult);
                    }
                }
            }
        }

        tripleTree.completeTree(resultTree);

    }

    /**
     * 根据问题树获取最终答案
     * 要求给定的问题树必须是归并完成形式
     * @param completedTree
     * @return
     */
    private AnswerVO doAsk(QuestionTree completedTree) throws AnswerException {
        return doAsk(completedTree, -1, 0);
    }
    private AnswerVO doAsk(QuestionTree completedTree,int step,int cur) throws AnswerException {
        //将List<Result>转化为List<RDFNode>逻辑封装
        Function<List<Result>,List<RDFNode>> resultList2RDFNodeList = (resultList)->{
            List<RDFNode> resourceList = new ArrayList<>();
            resultList.forEach((result)->{
                resourceList.add(result.getRdfNode());
            });
            return resourceList;
        };

        //单个MatchTree直接获取资源
        if(!(completedTree instanceof TripleTree)){
            ResultTree resultTree = (ResultTree) completedTree;
            List<RDFNode> resourceList = resultList2RDFNodeList.apply(resultTree.getResultList());
            return askAboutResource(resourceList,step,cur);
        }
        //TripleTree进行答案获取
        else {
            TripleTree tripleTree = (TripleTree) completedTree;
            //双层双?树 可以归为对单个资源的提问
            if(tripleTree.level()<=2 && tripleTree.isAskForResource()){
                ResultTree resultTree = (ResultTree) tripleTree.getArgumentSubTree();
                List<RDFNode> resourceList = resultList2RDFNodeList.apply(resultTree.getResultList());
                return askAboutResource(resourceList,step,cur);
            }
            return askAboutTripleTree(tripleTree,step,cur);
        }

    }

    /**
     * 多层三元组问句最终形式回答
     * @param tripleTree 最终形式的questionTripleTree
     * @return
     */
    private AnswerVO askAboutTripleTree(TripleTree tripleTree) throws AnswerException {
        return askAboutTripleTree(tripleTree,-1,0);
    }
    private AnswerVO askAboutTripleTree(TripleTree tripleTree,int step,int cur) throws AnswerException {

        //查询指针 只有划入(cur,cur+step)区间时才会获取答案
        int searchPoint = 0;

        //当前解析节点一定是TargetTree
        ResultTree resultTree = (ResultTree) tripleTree.getTargetSubTree();

        //如果当前节点没有任何答案 则取下一个有答案的节点
        if(resultTree.getResultList().size()==0 &&
                (tripleTree.getArgumentSubTree() instanceof TripleTree))
        {
            return askAboutTripleTree((TripleTree)tripleTree.getArgumentSubTree(),step,cur);
        }

        Answer mainAnswer = null;
        List<Answer> mainAnswerList = null;

        List<Result> resultList; //临时变量

        //为每一条答案产生一个可能的回答
        resultList = resultTree.getResultList();
        for (Result value : resultList) {
            if (searchPoint < cur) {
                searchPoint++;
                continue;
            } else if (step != -1 && searchPoint >= cur + step) {
                break;
            } else searchPoint++;

            TargetResult targetResult = (TargetResult) value;
            Answer answer = onePathAnswer(targetResult);

            if(mainAnswer==null) {
                mainAnswer = new Answer();
                mainAnswerList = new LinkedList<>();
                mainAnswer.setAnswerListContent(mainAnswerList);
            }
            mainAnswerList.add(answer);
        }

        //暂时生成空的additionAnswer
        Answer additionAnswer = null;
        List<Answer> additionAnswerList = null;

        //当搜索位置尚未超出需求边界 继续添加附加答案
        resultList = ((ResultTree)tripleTree.getArgumentSubTree()).getResultList();
        for (Result value : resultList) {
            if (searchPoint < cur) {
                searchPoint++;
                continue;
            } else if (step != -1 && searchPoint >= cur + step) {
                break;
            } else searchPoint++;

            RDFNode rdfNode = value.getRdfNode();
            if (!rdfNode.isResource()) continue;
            Resource resource = rdfNode.asResource();
            Resource type = datasetManager.getRDFBaseType(resource);

            if (RDFS.Resource.equals(type)) {
                if(additionAnswer==null) {
                    additionAnswer = new Answer();
                    additionAnswerList = new LinkedList<>();
                    additionAnswer.setAnswerListContent(additionAnswerList);
                }
                Answer answer = getAnswerAboutResource(resource);
                additionAnswerList.add(answer);
            } else if (RDFS.Class.equals(type)) {
                if(additionAnswer==null) {
                    additionAnswer = new Answer();
                    additionAnswerList = new LinkedList<>();
                    additionAnswer.setAnswerListContent(additionAnswerList);
                }
                Answer answer = getAnswerAboutType(resource);
                additionAnswerList.add(answer);
            }
        }

        //汇总答案
        AnswerVO answerVO = new AnswerVO();
        answerVO.setMainAnswer(mainAnswer);
        answerVO.setAdditionAnswer(additionAnswer);
        return answerVO;
    }

    /**
     * 获取当前问题所能产生的答案个数
     * @param questionTree
     * @return
     */
    private int askForResultCount(QuestionTree questionTree) {
        QuestionTree completedTree;
        try {
            completedTree = completeTree(questionTree);
        } catch (QuestionTreeException questionTreeException) {
            return 0;
        }

        //单个MatchTree直接获取资源
        if(!(completedTree instanceof TripleTree)){
            ResultTree resultTree = (ResultTree) completedTree;
            return resultTree.getResultList().size();
        }
        //TripleTree进行答案获取
        else {
            TripleTree tripleTree = (TripleTree) completedTree;
            //双层双?树 可以归为对单个资源的提问
            if(tripleTree.level()<=2 && tripleTree.isAskForResource()){
                ResultTree resultTree = (ResultTree) tripleTree.getArgumentSubTree();
                return resultTree.getResultList().size();
            } else {
                while (true) {
                    //当前解析节点一定是TargetTree
                    ResultTree resultTree = (ResultTree) tripleTree.getTargetSubTree();
                    //如果当前节点没有任何答案 则取下一个有答案的节点
                    if(resultTree.getResultList().size()==0 &&
                            (tripleTree.getArgumentSubTree() instanceof TripleTree))
                    {
                        tripleTree = (TripleTree)tripleTree.getArgumentSubTree();
                    } else {
                        return resultTree.getResultList().size();
                    }
                }
            }
        }

    }

    /**
     * 某个最终资源所经过路径上的回答信息
     * @param targetResult
     * @return
     */
    private Answer onePathAnswer(TargetResult targetResult) throws AnswerException {
        //设置结果集
        Answer answer = new Answer();
        List<MultipleContent.Item> itemList = new ArrayList<>();
        Set<String> recommendList = new HashSet<>();
        KG kg = new KG();
        answer.setMultipleContent(itemList,recommendList,kg);

        Result curResult = targetResult;
        while (true){
            RDFNode rdfNode = curResult.getRdfNode();
            //为当前节点建立图谱路径
            if(rdfNode.isResource()) {
                kgBuilder.buildResourceRelation(rdfNode.asResource(), kg);
//                recommendList.addAll(getRecommends(rdfNode.asResource()));
            }
            //已经到达最终节点 结束答案探索
            if(curResult instanceof RDFNodeResult) {
                break;
            }
            //仍有结果来源
            else { //if(curResult instanceof TargetResult)
                TargetResult curTr = (TargetResult) curResult;
                Statement statement = curTr.getStatement();

                //生成主语节点
                Node subjectNode = kgBuilder.transformResource2Node(statement.getSubject());

                //生成宾语节点
                Node objectNode;
                RDFNode object = statement.getObject();
                if(object.isResource()) {
                    objectNode = kgBuilder.transformResource2Node(object.asResource());
                }
                else {
                    objectNode = kgBuilder.transformLiteral2Node(object.asLiteral());
                }

                //生成三元组
                Triple triple = kgBuilder.transformProperty2Triple(
                        statement.getPredicate(), subjectNode, objectNode);

                MultipleContent.Item item = new MultipleContent.Item();
                item.setSubject(subjectNode);
                item.setTriple(triple);
                item.setObject(objectNode);
                itemList.add(item);

                if(curTr.getSourceSubject()!=null) curResult = curTr.getSourceSubject();
                else curResult = curTr.getSourceObject();

            }

        }

        if(targetResult.getRdfNode().isResource()){
            kgBuilder.buildTypeOfResourceRelation(targetResult.getRdfNode().asResource(),kg);
        }

        return answer;
    }

    /**
     * (subject,null,null)形式问句
     * @param subjectStr
     * @return
     */
    private AnswerVO askAboutResource(String subjectStr) throws AnswerException {
        List<RDFNode> subjectList = datasetManager.getResourceByLabel(subjectStr);
        return askAboutResource(subjectList);
    }
    private AnswerVO askAboutResource(List<RDFNode> subjectList) throws AnswerException {
        return askAboutResource(subjectList,-1,0);
    }
    private AnswerVO askAboutResource(List<RDFNode> subjectList,int step,int cur) throws AnswerException{
        //查询指针 只有划入(cur,cur+step)区间时才会获取答案
        int searchPoint = 0;

        //生成主要回答对象
        Answer mainAnswer = null;
        List<Answer> answerList = null;

        //subjectStr标签所对应的资源可能有多个 每个资源会生成一个Answer添加至mainAnswer的list中
        for (RDFNode node : subjectList) {
            if (searchPoint < cur) {
                searchPoint++;
                continue;
            } else if (step != -1 && searchPoint >= cur + step) {
                break;
            } else searchPoint++;

            if (!node.isResource()) continue;
            Resource resource = node.asResource();
            Resource type = datasetManager.getRDFBaseType(resource);
            if (RDFS.Resource.equals(type)) {
                if (mainAnswer==null) {
                    mainAnswer = new Answer();
                    answerList = new LinkedList<>();
                    mainAnswer.setAnswerListContent(answerList);
                }
                Answer answer = getAnswerAboutResource(resource);
                answerList.add(answer);
            } else if (RDFS.Class.equals(type)) {
                if (mainAnswer==null) {
                    mainAnswer = new Answer();
                    answerList = new LinkedList<>();
                    mainAnswer.setAnswerListContent(answerList);
                }
                Answer answer = getAnswerAboutType(resource);
                answerList.add(answer);
            }
        }

        AnswerVO answerVO = new AnswerVO();
        answerVO.setMainAnswer(mainAnswer);
        answerVO.setAdditionAnswer(null);

        return answerVO;
    }

    /**
     * 关于资源所有信息的答案
     *  不是指RDF的资源类，而是区别于Class、Property的资源实例
     * @param resource 必须是uri /R..结尾的资源
     * @return
     */
    private Answer getAnswerAboutResource(Resource resource) throws AnswerException {
        //当前resource图谱信息
        KG kg = new KG();

        //延伸节点关系
        kgBuilder.buildResourceRelation(resource,kg);

        //延伸class依赖关系(type、domain、range关系)
        Resource type = datasetManager.getType(resource);
        kgBuilder.buildTypeRelation(type,kg);

        //作为图谱源节点
        Node resourceNode = kgBuilder.transformResource2Node(resource);
        for (Node node : kg.getNodes()) {
            if (node.getUri().equals(resourceNode.getUri())) {
                resourceNode = node;
                break;
            }
        }

        //当前resource的所有信息的答案
        Answer answer = new Answer();
        answer.setResourceContent(resourceNode,new HashSet<>(),kg);

        return answer;
    }

    /**
     * 关于某个类型的所有信息的答案
     * @return
     */
    private Answer getAnswerAboutType(Resource type) throws AnswerException {
        //当前resource图谱信息
        KG kg = new KG();

        //作为图谱源节点
        Node typeNode = kgBuilder.transformResource2Node(type);

        //延伸类型关系
        kgBuilder.buildTypeRelation(type,kg);

        //当前type的所有信息的答案
        Answer answer = new Answer();
        answer.setClassContent(typeNode,new HashSet<>(),kg);

        return answer;
    }

    /**
     * 生成问题推荐
     * @return
     */
    private Set<String> getRecommends(Resource focusResource) {
        Statement typeStatement = datasetManager.getProperty(focusResource, RDF.type);
        if(typeStatement==null)return new HashSet<>();
        Resource typeResource = typeStatement.getObject().asResource();
        String resourceLabel = datasetManager.getLabel(focusResource,"zh");

        List<Statement> domainList = datasetManager.listStatements(null,RDFS.domain,typeResource);
        List<Statement> rangeList = datasetManager.listStatements(null,RDFS.range,typeResource);

        Set<String> stringHashSet = new HashSet<>();

        for(Statement statement:domainList) {
            Resource property = statement.getSubject();
            if(property.getURI().equals(DefaultURI.sameAs)) continue;
            String label = datasetManager.getLabel(property, "zh");
            try {
                QuestionTree questionTree = new TripleTree(
                        new LabelTree(resourceLabel),new LabelTree(label),null
                );
                if(askForResultCount(questionTree) > 0) {
                    String question = resourceLabel + "的" + label + "是什么?";
                    stringHashSet.add(question);
                }
            } catch (QuestionTreeException ignored) { }
        }

        for (Statement statement:rangeList) {
            Resource property = statement.getSubject();
            if(property.getURI().equals(DefaultURI.sameAs)) continue;
            String label = datasetManager.getLabel(property, "zh");
            try {
                QuestionTree questionTree = new TripleTree(
                        null,new LabelTree(label),new LabelTree(resourceLabel)
                );
                if(askForResultCount(questionTree) > 0) {
                    String question = "什么的" + label + "是" + resourceLabel + "?";
                    stringHashSet.add(question);
                }
            } catch (QuestionTreeException ignored) { }
        }

        return stringHashSet;
    }


}
